local camera = require"library/camera"
local bump = require"library/bump/bump"
local depth_buffer = require"base/depth_list"
local Signal = oopsys.getcls("Signal")
local SerObject = require"core/module/filesys/serobject"
local NetworkObject = require"core/module/network/network_object"
local SceneArea = require"core/module/grun/scene/area"

local scene = class("Scene",Signal,SerObject,NetworkObject){
    camera = nil,
    bump_world = nil,
    depth_buffer = nil,
    areas = {},
    objects = {},
    loading_center = {x = 0,y = 0}
}

for i,name in ipairs(LOVE_CALLBACK) do
    if scene[name] == nil then
        scene[name] = function(self,...)
            self:iter_objects(function(obj,...)
                if obj[name] then
                    obj[name](obj,...)
                end
            end,...)
        end
    end
end

function scene:__init__()
    self.camera = camera(0,0)
    self.bump_world = bump.newWorld(WORLD_AREA_PSIZE)
    self.depth_buffer = depth_buffer(64)

    self:signal("not_area")
    self:signal("add_object")
    self:signal("remove_object")
end

function scene:__reg_event__()
    self:create_event("synchronize_area")
    :request({"ax","ay"},function(pack)
        local scene = core.grun:get_scene()
        local area = self:get_area(pack.ax,pack.ay,true)
        local event_name = scene:get_event_name("synchronize_area")
        if not area then
            area = SceneArea(pack.ax,pack.ay)
            self:add_area(area)
        end
        peer:send(event_name,{ax,ay,area.tiles})
        for name,object in pairs(area.objects) do
            local event_name = object:get_event_name("synchronize")
            peer:send(event_name,object:__synchronize_data__())
        end
    end)
    :feedback({"ax","ay","tiles"},function(pack)
        local ax,ay = pack.ax,pack.ay
        local scene = core.grun:get_scene()
        local area = scene:get_area(ax,ay)
        if not area then
            area = SceneArea(ax,ay)
            scene:add_area(area)
        end
        area.tiles = pack.tiles
    end):reg()
end


function scene:wposi_to_aposi(x,y)
    return math.floor(x / WORLD_AREA_PSIZE),math.floor(y / WORLD_AREA_PSIZE)
end

function scene:aposi_to_wposi(ax,ay)
    return ax * WORLD_AREA_PSIZE,ay * WORLD_AREA_PSIZE
end

function scene:add_area(area)
    if not area then return end
    local x,y = area.ix,area.iy
    if not self.areas[x] then
        self.areas[x] = {}
    end
    self.areas[x][y] = area
end

function scene:add_area_from_wposi(x,y)
    x,y = self:wposi_to_aposi(x,y)
    return self:get_area(x,y)
end

function scene:load_area(ax,ay)
end

function scene:unload_area(area)

end

function scene:get_area(ax,ay,is_load)
    if not self.areas[ax] then
        self.areas[ax] = {}
    end
    local area = self.areas[ax][ay]

    if core.core_mode == CORE_MODE_SERVER then
        if not area and is_load then
            area = load_area(ax,ay)
            self:add_area(area)            
        end
    end

    if not area then
        area = SceneArea(ax,ay)
        self:add_area(area)
    end

    return area
end

function scene:set_ldcn(x,y)
    self.loading_center.x = x or self.loading_center.x
    self.loading_center.y = y or self.loading_center.y
end

function scene:set_ldcn_from_wposi(x,y)
    x,y = self:wposi_to_aposi(x,y)
    self.loading_center.x = x or self.loading_center.x
    self.loading_center.y = y or self.loading_center.y
end

function scene:get_object(name)
    if not name then return nil end
    return self.objects[name]
end

function scene:add_object(object)
    local name = object.name
    if not name then
        name = tostring(object)
        object.name = name
    end
    if self.objects[name] ~= nil then return end

    local ax,ay = self:wposi_to_aposi(object.x,object.y)
    local area = self:get_area()
    area:add(object)
    object._area = area
    self.objects[name] = objects
    self.depth_buffer:index_insert(object,object,object.depth or 0)
    return self
end

function scene:remove_object(object)
    if type(object) == "string" then
        object = self:get_object(object)
    end
    if object == nil then return end
    self.objects[object.name] = nil
    object._area:remove(object)
    self.depth_buffer:index_remove(object)
    return self
end

function scene:move_object(object,vx,vy)
    if vx+vy == 0 then return end
    local old_area = self:add_area_from_wposi(object.x,object.y)
    object.x = object.x + vx
    object.y = object.y + vy
    local new_area = self:add_area_from_wposi(object.x,object.y)
    if old_area == new_area then return end
    old_area:remove(object)
    new_area:add(object)
end

function scene:moveto_object(object,x,y)
    local old_area = self:add_area_from_wposi(object.x,object.y)
    object.x = x
    object.y = y
    local new_area = self:add_area_from_wposi(object.x,object.y)
    if old_area == new_area then return end
    old_area:remove(object)
    new_area:add(object)
end

function scene:iter_loading_areas(func,...)
    local bx,by = self.loading_center.x - 1,self.loading_center.y - 1
    for x = bx,bx + 2 do
        for y = by,by + 2 do
            local area = self:get_area(x,y)
            func(area,...)
        end
    end
end

function scene:iter_objects(func,...)
    local bx,by = self.loading_center.x - 1,self.loading_center.y - 1
    for x = bx,bx + 2 do
        for y = by,by + 2 do
            local area = self:get_area(x,y)
            for obj in pairs(area.objects) do
                func(obj,...)
            end
        end
    end
end

function scene:update(dt)
    self.camera:update(dt)
    self:iter_objects(function(object)
        if objects.update then
            objects:update()
            local draw_node = self.depth_buffer.__node_index[object]
            draw_node.depth = objects.depth
            self.depth_buffer:_update_node_depth(draw_node)
        end
    end)
end

local function _is_draw(self,obj)
    local oax,oay = self:world_position_to_area_position(obj.x,obj.y)
    local ax,ay = self.loading_center.x - 1,self.loading_center.y - 1
    return not (
        oax < ax or
        oay < ay or
        oax > ax + 2 or 
        oay > ay + 2
    )
end

function scene:draw()
    --if core.core_mode == CORE_MODE_CLIENT then
        self.camera:draw_begin()

        self:iter_loading_areas(function(area)
            area:draw(self.camera)
        end)

        for node in self.depth_buffer:items() do
            local object = node.data
            if _is_draw(object) then
                self.depth_buffer:index_remove(node) 
            else
                object:draw(self.camera)
            end
        end

        self.camera:draw_end()
    --end
end

return scene:reg()

